package com.generic.mybatis.dal.interceptor.automap;

import com.generic.mybatis.dal.dao.annotation.Exclude;
import com.generic.mybatis.dal.dao.annotation.Join;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Id;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;

/**
 * 表信息获取
 *
 * @author wuaj
 */
@Component
public class TableMapperProviderDefault implements TableMapperProvider, ApplicationContextAware {

    /**
     * 缓存表的相关信息，如表名、字段名等
     */
    private static Map<Class<?>, TableMapper> tableMapperCache = Maps.newLinkedHashMapWithExpectedSize(123);

    private ApplicationContext applicationContext;

    /**
     * 由传入的dto对象的class构建TableMapper对象，构建好的对象存入缓存中，以后使用时直接从缓存中获取
     *
     * @param dtoClass
     * @return TableMapper
     */
    @Override
    public TableMapper getTableMapper(Class<?> dtoClass) {
        synchronized (tableMapperCache) {
            TableMapper tableMapper = tableMapperCache.get(dtoClass);
            if (tableMapper != null) return tableMapper;

            tableMapper = new TableMapper();
            applicationContext.getAutowireCapableBeanFactory().autowireBean(tableMapper);

            // DTO类设置
            tableMapper.setDtoClass(dtoClass);

            // 表名读取并设置
            Table table = dtoClass.getAnnotation(Table.class);
            if (StringUtils.isBlank(table.name())) {
                tableMapper.setTableName(dtoClass.getSimpleName());
            } else {
                tableMapper.setTableName(table.name().trim());
            }

            // 字段名获取并设置
            buildFieldMaps(dtoClass, tableMapper);
            // 保存表信息
            tableMapperCache.put(dtoClass, tableMapper);
            return tableMapper;
        }
    }

    private void buildFieldMaps(Class<?> dtoClass, TableMapper tableMapper) {
        FieldMapper fieldMapper;
        List<Field> fields = getAllFields(dtoClass);

        Map<String, FieldMapper> fieldMapperCache = Maps.newTreeMap();
        if (tableMapper.getFieldMapperCache() != null) {
            fieldMapperCache.putAll(tableMapper.getFieldMapperCache());
        }

        List<FieldMapper> fieldMapperList = Lists.newArrayList();
        for (Field field : fields) {
            boolean isContinue = isIgnore(field);
            if (!isContinue) {
                if (field.isAnnotationPresent(Id.class)) {
                    tableMapper.addId(field.getName());
                }
                Column column = field.getDeclaredAnnotation(Column.class);
                fieldMapper = new FieldMapper(tableMapper);
                fieldMapper.setField(field);
                fieldMapper.setFieldName(field.getName());
                if (column != null && StringUtils.isNotBlank(column.name())) {
                    fieldMapper.setDbFieldName(column.name().trim());
                } else {
                    if (field.isAnnotationPresent(EmbeddedId.class)) {
                        tableMapper.addPkInfo(field);
                        isContinue = true;
                    } else if (field.isAnnotationPresent(Join.class)) {
                        //do join
                        tableMapper.addJoinInfo(field, field.getAnnotation(Join.class));
                        isContinue = true;
                    } else {
                        // always use cameCase format
                        fieldMapper.setDbFieldName(StringUtils.join
                                (StringUtils.splitByCharacterTypeCamelCase(field.getName()), "_").toLowerCase());
                    }
                }
                if (!isContinue) {
                    fieldMapperCache.put(fieldMapper.getFieldName(), fieldMapper);
                    fieldMapperList.add(fieldMapper);
                }
            }
        }
        tableMapper.setFieldMapperCache(fieldMapperCache);
    }

    private boolean isIgnore(Field field) {
        return Modifier.isTransient(field.getModifiers()) ||
                field.isAnnotationPresent(Exclude.class) ||
                Modifier.isStatic(field.getModifiers());
    }

    private List<Field> getAllFields(Class dto) {
        List<Class> superClazzList = Lists.newArrayList();

        if (dto == null) {
            return Lists.newArrayList();
        }

        if (!Object.class.equals(dto)) {
            superClazzList.add(0, dto);
        }
        // 直到超类是object为止
        while (dto.getSuperclass() != null && !Object.class.equals(dto.getSuperclass())) {
            dto = dto.getSuperclass();
            superClazzList.add(0, dto);
        }

        List<Field> fields = Lists.newArrayList();
        for (Class superClazz : superClazzList) {
            for (Field field : superClazz.getDeclaredFields()) {
                fields.add(field);
            }
        }
        return fields;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        TableMapperHolder.setProvider(this);
    }

}
